import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { uploadConfig } from './uploadServiceConfig'

const downloadFileByGuid = async (
  guid: string,
  token: string,
  progressCallback?: (progressNum: number) => void,
  failedCallback?: (error: { code: number; errorMsg: string }) => void,
) => {
  try {
    const res = await getChunkUrlAryByGuid(guid, token)
    if (res.code !== 200) {
      const error = {
        code: res.code,
        errorMsg: res.msg || '请求失败，未知错误',
      }
      throw error
    }

    const { fileName, md5, urls } = res.data
    if (!fileName || !md5 || !urls) {
      const error = {
        code: 404,
        errorMsg: '数据错误：文件名、MD5或URL列表为空，请检查 GUID 是否正确。',
      }
      throw error
    }

    const downloader = new ChunksDownloader(
      {
        token,
        fileName,
        md5,
        urls,
        guid,
      },
      progressCallback,
    )
    downloader.download()
    return downloader
  } catch (error: any) {
    const handledError = {
      code: error.code || 500,
      errorMsg: error.errorMsg || error.message || '未知错误发生',
    }
    console.error('下载失败：', handledError)
    if (failedCallback) {
      failedCallback(handledError)
    } else {
      throw handledError // 如果没有回调则直接抛出
    }
  }
}

const getChunkUrlAryByGuid = async (guid: string, token: string) => {
  const url = uploadConfig.baseUrl + uploadConfig.getDownloadChunkUrls + guid
  return axios
    .request({
      url,
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'access-token': token,
      },
    })
    .then(res => {
      return res.data
    })
}

interface ChunkUrlData {
  url: string
  path: string
}
type DownloadStatus =
  | 'Downloading'
  | 'Downloaded'
  | 'Wait'
  | 'Paused'
  | 'Failed'
interface DownloadTask {
  url: string
  blob: ArrayBuffer | undefined
  // blob: Blob | undefined
  index: number // chunkIndex
  status: DownloadStatus
}

class ChunksDownloader {
  fileName: string
  md5: string
  guid: string
  chunkUrlAry: ChunkUrlData[]
  downloadTasks: DownloadTask[] // 待下载
  downloadingTasks: DownloadTask[] // 下载中
  finishedTasks: DownloadTask[] // 下载完成
  errorTasks: DownloadTask[] // 下载完成
  errorMsg: string
  MaxParallelDownloads: number // 最大并行下载
  downloadUrl?: string
  progressCb: (processNum: number) => void
  progress: number
  token: string
  constructor(
    options: {
      md5: string
      fileName: string
      urls: ChunkUrlData[]
      token: string
      guid: string
    },
    progressCb = (processNum: number) => {
      //
    },
  ) {
    const { fileName, md5, urls, token, guid } = options
    this.fileName = fileName
    this.guid = guid
    this.md5 = md5
    this.chunkUrlAry = urls
    this.progress = 0
    this.downloadTasks = []
    this.finishedTasks = []
    this.errorTasks = []
    this.errorMsg = ''
    this.downloadingTasks = []
    this.MaxParallelDownloads = 2
    this.token = token
    this.progressCb = progressCb
  }
  setMaxParallel(num: number) {
    this.MaxParallelDownloads = num
  }
  prepareDownloadTasks() {
    // chunkUrlAry => downloadTasks
    this.chunkUrlAry.map((urlData, index) => {
      const url = urlData.url + urlData.path
      this.downloadTasks.push({
        url,
        blob: undefined,
        index,
        status: 'Wait',
      })
    })
  }
  updateTasks() {
    while (this.downloadingTasks.length < this.MaxParallelDownloads) {
      const task = this.downloadTasks.shift()
      if (task !== undefined) {
        this.downloadingTasks.push(task)
      } else {
        return this.finishedDownload()
      }
      this.progressCb(this.progress)
    }
  }
  async finishedDownload() {
    this.progressCb(this.progress)
    const aryBufAry: ArrayBuffer[] = []
    if (this.finishedTasks.length === this.chunkUrlAry.length) {
      const spark = new SparkMD5.ArrayBuffer()
      const sortedTasks = this.finishedTasks.sort((taskA, taskB) => {
        return taskA.index - taskB.index
      })
      sortedTasks.map(async task => {
        if (task.blob) {
          aryBufAry.push(task.blob)
          spark.append(task.blob)
        }
      })
      const md5 = spark.end()
      // valid md5  same to download  or  chunk order is wrong
      if (md5 === this.md5) {
        const url = URL.createObjectURL(new Blob(aryBufAry))
        this.downloadToLocal(url)
      } else {
        console.error(' chunk order error chunks 组装错误!')
        this.errorMsg = '文件MD5不匹配！数据错误。'
      }
    }
  }
  downloadToLocal(url: string) {
    // FileSaver.js 代替
    const link = document.createElement('a')
    link.href = url
    link.setAttribute('download', this.fileName) //or any other extension
    document.body.appendChild(link)
    link.click()
    link.remove()
    this.destroy()
  }
  rmFromAry(item: DownloadTask, itemAry: DownloadTask[]) {
    const idx = itemAry.indexOf(item)
    if (idx !== -1) {
      itemAry.splice(idx, 1)
    }
  }
  uploadNext(finishedTask: DownloadTask) {
    this.rmFromAry(finishedTask, this.downloadingTasks)
    this.finishedTasks.push(finishedTask)
    const progress = (
      (this.finishedTasks.length / this.chunkUrlAry.length) *
      100
    ).toFixed(1)
    this.progress = parseInt(progress)
    this.updateTasks()
    this.updateDownloading()
  }
  failed() {
    this.finishedTasks = []
    this.downloadTasks = []
    this.downloadingTasks = []
    this.progress = 0
    this.progressCb(this.progress)
  }
  updateDownloading() {
    this.downloadingTasks.map(task => {
      const { url } = task
      if (task.status === 'Downloading') {
        return false
      } else {
        task.status = 'Downloading'
        axios
          .get(url, {
            // responseType: 'blob',
            responseType: 'arraybuffer',
            headers: {
              'access-token': this.token,
            },
          })
          .then(
            res => {
              if (res.status === 200) {
                task.blob = res.data
                task.status = 'Downloaded'
                this.uploadNext(task)
              } else {
                // 一片失败也就是全都失败了 没有断点续传 重新下载
                task.status = 'Failed'
                if (res.data.message) {
                  this.errorMsg = res.data.message
                  console.error(this.errorMsg)
                }
                this.errorTasks.push(task)
                this.failed()
              }
            },
            err => {
              console.error(err)
              task.status = 'Failed'
              this.errorTasks.push(task)
              this.errorMsg = '网络错误！'
              this.failed()
            },
          )
      }
    })
  }
  startDownload() {
    this.prepareDownloadTasks()
    this.updateTasks()
    this.updateDownloading()
  }
  download() {
    this.startDownload()
  }
  destroy() {
    this.finishedTasks = []
    this.downloadTasks = []
    this.downloadingTasks = []
    this.errorTasks = []
    this.progress = 0
    this.finishedTasks = []
    this.chunkUrlAry = []
  }
}

export { getChunkUrlAryByGuid, downloadFileByGuid }
export default ChunksDownloader
